﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.LCS.AIDCCodes.Common
{
    /// <summary>
    /// Factory Encoder
    /// </summary>
    internal sealed class CodeEncoder : IDisposable
    {
        #region Internal static řízení encodování. Dispose.
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        internal static CodeMatrix Encode(string content, string codeName)
        {
            CodeType codeType = GetCodeType(codeName, null);
            return _RunEncode(content, codeType);
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        /// <returns></returns>
        internal static CodeMatrix Encode(string content, CodeType codeType)
        {
            return _RunEncode(content, codeType);
        }
        /// <summary>
        /// Return true when specified code can be encoded (exists appropriate encoder).
        /// </summary>
        /// <param name="codeName"></param>
        /// <returns></returns>
        internal static bool CanEncode(string codeName)
        {
            CodeType codeType = GetCodeType(codeName, CodeType.None);
            if (codeType == CodeType.None) return false;
            return CanEncode(codeType);
        }
        /// <summary>
        /// Return true when specified code type can be encoded (exists appropriate encoder).
        /// </summary>
        /// <param name="codeType"></param>
        /// <returns></returns>
        internal static bool CanEncode(CodeType codeType)
        {
            return (_ExistsEncoderEngine(codeType));
        }
        /// <summary>
        /// Performs encoding of text (content) with specified ErrorCorrectionLevel, 
        /// and return matrix of corresponding QR code.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="correctionLevel"></param>
        /// <param name="useMatrixPattern"></param>
        /// <returns></returns>
        private static CodeMatrix _RunEncode(string content, CodeType codeType)
        {
            CodeMatrix matrix = null;
            using (CodeEncoder qEncoder = new CodeEncoder(codeType))
            {
                qEncoder._Encode(content, codeType);
                matrix = qEncoder.Matrix;
            }
            return matrix;
        }
        /// <summary>
        /// Convert a code name to CodeType. I.e. "EAN13" = CodeType.EAN13.
        /// If convert failed: when exists defaultValue, its returned; else appropriate exception is thrown.
        /// </summary>
        /// <param name="codeName">Name of CodeType, case-insensitive.</param>
        /// <param name="defaultValue">Optionally default value for not recognized codeName.</param>
        /// <returns></returns>
        internal static CodeType GetCodeType(string codeName, CodeType? defaultValue)
        {
            if (String.IsNullOrEmpty(codeName))
            {
                if (defaultValue.HasValue) return defaultValue.Value;
                throw new ArgumentNullException("codeName", "Parameter \"codeName\" is empty.");
            }

            CodeType codeType;
            if (Enum.TryParse<CodeType>(codeName, true, out codeType))
                return codeType;

            if (defaultValue.HasValue) return defaultValue.Value;
            throw new ArgumentException("Value of \"codeName\" = " + codeName + " is not recognized as BarCode type.", "codeName");
        }
        private CodeEncoder(CodeType codeType)
        {
            this._InitTimeInfo();
            this._IEncoderEngine = GetEncoderEngine(codeType, true);
        }
        void IDisposable.Dispose()
        {
            this._DisposeTimeInfo();
        }
        #endregion
        #region IEncoderEngine : adapter to IEncoderEngine method and properties
        /// <summary>
        /// Encode content with CodeType
        /// </summary>
        /// <param name="content"></param>
        /// <param name="codeType"></param>
        private void _Encode(string content, CodeType codeType)
        {
            this._Timer.Start();
            this._IEncoderEngine.Encode(content, codeType);
            this._Timer.Stop();
            this._AddTimeInfo("Encoding value " + content);
        }
        /// <summary>
        /// Matrix of BarCode
        /// </summary>
        internal CodeMatrix Matrix
        {
            get { return this._IEncoderEngine.Matrix; }
        }
        private IEncoderEngine _IEncoderEngine;
        #endregion
        #region Factory method for create encoder of specified CodeType
        /// <summary>
        /// Return true if exists encoder for given CodeType.
        /// </summary>
        /// <param name="codeType"></param>
        /// <returns></returns>
        private static bool _ExistsEncoderEngine(CodeType codeType)
        {
            if (_EncoderEngines == null)
                _EncoderEngines = _CreateEncodeEngineDict();
            IEncoderEngine encoderEngine;
            if (_EncoderEngines.TryGetValue(codeType, out encoderEngine))
            {
                return (encoderEngine != null);
            }
            return false;
        }
        /// <summary>
        /// Create a new instance for bar code encoder for CodeType.
        /// Use a once created (as singleton) static table, where for each CodeType is stored a System.Type of apprpriate encoder.
        /// </summary>
        /// <param name="codeType"></param>
        /// <returns></returns>
        private static IEncoderEngine GetEncoderEngine(CodeType codeType, bool cached)
        {
            if (_EncoderEngines == null)
                _EncoderEngines = _CreateEncodeEngineDict();
            IEncoderEngine encoderEngine;
            if (_EncoderEngines.TryGetValue(codeType, out encoderEngine))
            {
                if (encoderEngine == null)
                    throw new InvalidProgramException("Encoder for CodeType=" + codeType.ToString() + " is NULL in current assembly.");

                // Can return cached engine instance? 
                if (cached) return encoderEngine;

                // Must return a new instance of engine:
                Type encoderType = encoderEngine.GetType();
                IEncoderEngine iEngine = (System.Activator.CreateInstance(encoderType) as IEncoderEngine);
                if (iEngine == null)
                    throw new InvalidProgramException("Encoder for CodeType=" + codeType.ToString() + " (" + encoderType.Namespace + "." + encoderType.Name + ") is not created.");
                return iEngine;
            }
            throw new InvalidProgramException("Encoder for CodeType=" + codeType.ToString() + " does not exists in current assembly.");
        }
        /// <summary>
        /// Create and return a dictionary, containing list of all encoders (implementing IBarcode), with key = CodeType and value = Type of encoder.
        /// If there is more than one encoder for one CodeType, then in result will be first Type.
        /// </summary>
        /// <returns></returns>
        private static Dictionary<CodeType, IEncoderEngine> _CreateEncodeEngineDict()
        {
            Dictionary<CodeType, IEncoderEngine> encoderDict = new Dictionary<CodeType, IEncoderEngine>();
            Type iBarcodeType = typeof(IEncoderEngine);
            Type[] types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes();
            foreach (Type type in types)
            {
                if (type.IsClass && !type.IsAbstract && type.GetInterfaces().Any(i => i == iBarcodeType))
                {
                    try
                    {
                        IEncoderEngine sample = (System.Activator.CreateInstance(type) as IEncoderEngine);
                        if (sample != null)
                        {
                            foreach (CodeType codeType in sample.EncoderForTypes)
                            {
                                if (!encoderDict.ContainsKey(codeType))
                                    encoderDict.Add(codeType, sample);
                            }
                        }
                    }
                    catch
                    { }
                }
            }
            return encoderDict;
        }
        private static Dictionary<CodeType, IEncoderEngine> _EncoderEngines;
        #endregion
        #region TimeDiagnostic
        private void _InitTimeInfo()
        {
            this._TimeFrequency = (decimal)System.Diagnostics.Stopwatch.Frequency;
            this._TimeInfoList = new List<KeyValuePair<string, decimal>>();
            this._Timer = System.Diagnostics.Stopwatch.StartNew();
        }
        private void _AddTimeInfo(string name)
        {
            long ticks = _Timer.ElapsedTicks;
            decimal microsec = Math.Round((1000000m * (decimal)ticks / _TimeFrequency), 2);
            _TimeInfoList.Add(new KeyValuePair<string, decimal>(name, microsec));
            _Timer.Restart();
        }
        private void _DisposeTimeInfo()
        {
            this._Timer.Stop();
            this._Timer = null;
            var times = this._TimeInfoList;
        }
        System.Diagnostics.Stopwatch _Timer;
        List<KeyValuePair<string, decimal>> _TimeInfoList;
        decimal _TimeFrequency;
        #endregion
    }
}
